using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using Microsoft.ApplicationBlocks.Data;
using System.Xml;
using System.Collections;
using System.Data;

namespace Microsoft.SharedSource.SqlServer.MDDE.Repository
{
    public sealed class InstanceConfigurableComponent
    {
        #region Fields

        MDDETemplateInstance parentInstance;
        int id;
        string componentName;
        string componentClassId;
        string componentUri;
        string componentType;
        InstanceScalarPropertyCollection scalarProperties;
        InstanceMappingCollection instanceMappings;
        ComponentColumnCollections columnCollections;

        #endregion

        #region Properties

        public ComponentColumnCollections ColumnCollections
        {
            get { return columnCollections; }
        }

        public MDDETemplateInstance ParentInstance
        {
            get { return parentInstance; }
        }

        public int Id
        {
            get { return id; }
        }

        public string ComponentName
        {
            get { return componentName; }
        }

        public string ComponentClassId
        {
            get { return componentClassId; }
            set { componentClassId = value; }
        }

        public string ComponentUri
        {
            get { return componentUri; }
            set { componentUri = value; }
        }

        public string ComponentType
        {
            get { return componentType; }
            set { componentType = value; }
        }

        public InstanceScalarPropertyCollection ScalarProperties
        {
            get
            {
                if (this.scalarProperties == null)
                    scalarProperties =
                        new InstanceScalarPropertyCollection(this);
                return scalarProperties;
            }
        }

        public InstanceMappingCollection InstanceMappings
        {
            get
            {
                if (instanceMappings == null)
                    instanceMappings = new InstanceMappingCollection(this);
                return instanceMappings;
            }
        }

        #endregion

        #region Methods

        public InstanceConfigurableComponent(
            MDDETemplateInstance parentInstance, string componentType,
            string componentName, string componentUri)
            :
            this(parentInstance, componentType,
            componentName, componentUri, null) { }

        public InstanceConfigurableComponent(
            MDDETemplateInstance parentInstance, string componentType,
            string componentName, string componentUri, SqlTransaction tn)
        {
            this.parentInstance = parentInstance;
            this.componentType = componentType;
            this.componentName = componentName;
            this.componentUri =
                componentUri == null ? string.Empty : componentUri;
            this.scalarProperties =
                new InstanceScalarPropertyCollection(this);
            this.instanceMappings =
                new InstanceMappingCollection(this);
            // Create the column collections list.
            this.columnCollections =
                new ComponentColumnCollections(this);

            // Get the component info.
            SqlParameter[] sqlParams = new SqlParameter[4];
            sqlParams[0] = new SqlParameter(
                "@templateInstanceId", parentInstance.Id);
            sqlParams[1] = new SqlParameter(
                "@componentType", ComponentType);
            sqlParams[2] = new SqlParameter(
                "@componentName", ComponentName);
            sqlParams[3] = new SqlParameter(
                "@componentUri", ComponentUri);

            DataSet componentInfo;

            if (tn == null)
                componentInfo = SqlHelper.ExecuteDataset(
                parentInstance.ParentTemplate.
                ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetInstanceComponentInfo, sqlParams);
            else
                componentInfo = SqlHelper.ExecuteDataset(tn,
                CommandType.Text,
                Resource.GetInstanceComponentInfo, sqlParams);

            // We could not find the component.
            if (componentInfo.Tables[0].Rows.Count == 0)
                return;

            // We could find the component.
            DataRow r = componentInfo.Tables[0].Rows[0];
            this.componentClassId = r["ComponentClassId"].ToString();
            this.id = Convert.ToInt32(r["ComponentId"]);

            DataSet scalarPropertyNames;
            if (tn == null)
                scalarPropertyNames = SqlHelper.ExecuteDataset(
                parentInstance.ParentTemplate.
                ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetInstanceScalarPropertyNames,
                new SqlParameter("@componentId", this.Id));
            else
                scalarPropertyNames = SqlHelper.ExecuteDataset(tn,
                CommandType.Text,
                Resource.GetInstanceScalarPropertyNames,
                new SqlParameter("@componentId", this.Id));

            foreach (DataRow r0 in scalarPropertyNames.Tables[0].Rows)
            {
                // Create a scalar property object.
                InstanceScalarProperty objProp =
                    new InstanceScalarProperty(
                    this, r0["PropertyName"].ToString(), tn);

                // Add this property to list.
                this.scalarProperties.Add(objProp);
            }

            sqlParams = new SqlParameter[1];
            sqlParams[0] = new SqlParameter("componentId", this.Id);

            DataSet mappingSourceColumns = SqlHelper.ExecuteDataset(
                parentInstance.ParentTemplate.
                ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetMappingSourceColumnNames, sqlParams);

            // Add in each of the above mappings to internal list.
            foreach (DataRow r0 in mappingSourceColumns.Tables[0].Rows)
            {
                // Create a new mapping.
                InstanceMapping mapping =
                    new InstanceMapping(
                    this, r0["SourceColumnName"].ToString(), tn);

                // Add this mapping to list.
                this.instanceMappings.Add(mapping);
            }

            sqlParams = new SqlParameter[1];
            sqlParams[0] = new SqlParameter("componentId", this.Id);

            DataSet collectionTypes = SqlHelper.ExecuteDataset(
                parentInstance.ParentTemplate.
                ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetComponentCollectionTypesAndNames, sqlParams);

            // Add in each of the above collection types to internal list.
            foreach (DataRow r0 in collectionTypes.Tables[0].Rows)
            {
                // Create a new collection.
                InstanceColumnCollection collection =
                    new InstanceColumnCollection(
                    this, r0["CollectionType"].ToString(),
                    r0["CollectionName"].ToString(), tn);

                // Add this collection to list.
                this.columnCollections.Add(collection);
            }
        }

        internal void Create(SqlTransaction inputTxn)
        {
            // Create a new transaction if there is no input transaction.
            SqlTransaction txn = inputTxn != null ? inputTxn :
                parentInstance.ParentTemplate.ParentRepository.
                RepositoryConnection.BeginTransaction();

            try
            {
                SqlParameter[] sqlParams = new SqlParameter[5];
                sqlParams[0] =
                    new SqlParameter("@componentClassId",
                    this.ComponentClassId);
                sqlParams[1] =
                    new SqlParameter("@componentName",
                    this.ComponentName);
                sqlParams[2] =
                    new SqlParameter("@templateInstanceId",
                    this.ParentInstance.Id);
                sqlParams[3] =
                    new SqlParameter("@componentUri",
                    this.ComponentUri);
                sqlParams[4] =
                    new SqlParameter("@componentType",
                    this.ComponentType);

                this.id =
                    Convert.ToInt32(
                    SqlHelper.ExecuteScalar(txn,
                    CommandType.StoredProcedure,
                    "sp_CreateTemplateInstanceComponent",
                    sqlParams));

                // Create the component scalar properties.
                foreach (InstanceScalarProperty
                    prop in this.ScalarProperties)
                    prop.Create(txn);

                // Create the component mappings.
                foreach (InstanceMapping mapping
                    in this.InstanceMappings)
                    mapping.Create(txn);

                // Create the column collections.
                foreach (InstanceColumnCollection collection
                    in this.ColumnCollections)
                    collection.Create(txn);

                // Commit the transaction if it is not an input one.
                if (inputTxn == null)
                    txn.Commit();
            }
            catch (Exception ex)
            {
                // Rollback the transaction if it is not an input one.
                if (inputTxn == null)
                    txn.Rollback();
                throw ex;
            }
        }

        internal void Update(SqlTransaction inputTxn)
        {
            // Create a new transaction if there is no input transaction.
            SqlTransaction txn = inputTxn != null ? inputTxn :
                parentInstance.ParentTemplate.ParentRepository.
                RepositoryConnection.BeginTransaction();

            try
            {
                #region Update the component scalar properties.

                // Get the properties list.
                DataSet propertiesInfo =
                    SqlHelper.ExecuteDataset(txn, CommandType.Text,
                    Resource.GetInstanceScalarPropertyNames,
                    new SqlParameter[] { new SqlParameter("@componentId", this.Id) });

                List<string> propToBeDeleted = new List<string>();

                List<InstanceScalarProperty> propToBeUpdated =
                    new List<InstanceScalarProperty>();

                List<InstanceScalarProperty> propToBeInserted =
                    new List<InstanceScalarProperty>();

                foreach (DataRow r0 in propertiesInfo.Tables[0].Rows)
                {
                    // Find all the properties that 
                    // are now not present in the collection 
                    // and delete them from the repository.
                    if (!this.ScalarProperties.ContainsProperty(r0["PropertyName"].ToString()))
                        propToBeDeleted.Add(r0["PropertyName"].ToString());
                }

                bool propertyExists;
                foreach (InstanceScalarProperty property in this.ScalarProperties)
                {
                    propertyExists = false;

                    // Check if this property is already in the repository.
                    foreach (DataRow r0 in propertiesInfo.Tables[0].Rows)
                    {
                        if (r0["PropertyName"].ToString().Equals(
                            property.Name,
                            StringComparison.InvariantCultureIgnoreCase))
                        {
                            propToBeUpdated.Add(property);
                            propertyExists = true;
                            break;
                        }
                    }

                    // Find new members in the collection 
                    // and insert them into the repository.
                    if (!propertyExists)
                        propToBeInserted.Add(property);
                }

                // Delete the properties.
                foreach (string propertyName in propToBeDeleted)
                {
                    InstanceScalarProperty prop = new InstanceScalarProperty(this, propertyName);

                    prop.Delete(txn);
                }

                // Update the properties.
                foreach (InstanceScalarProperty prop in propToBeUpdated)
                    prop.Update(txn);

                // Insert new properties.
                foreach (InstanceScalarProperty prop in propToBeInserted)
                    prop.Create(txn);

                //foreach (InstanceScalarProperty
                //    prop in this.ScalarProperties)
                //    prop.Update(txn);

                #endregion

                #region Update the component mappings.
                // Get the mappings list.
                DataSet mappingsInfo =
                    SqlHelper.ExecuteDataset(txn,
                    CommandType.Text,
                    Resource.GetMappingSourceColumnNames,
                    new SqlParameter[] { new SqlParameter("@componentId", this.Id) });

                List<string> toBeDeleted =
                    new List<string>();

                List<InstanceMapping> toBeUpdated =
                    new List<InstanceMapping>();

                List<InstanceMapping> toBeInserted =
                    new List<InstanceMapping>();

                Hashtable repositoryMappings = new Hashtable();
                foreach (DataRow r0 in mappingsInfo.Tables[0].Rows)
                {
                    repositoryMappings.Add(r0["SourceColumnName"], null);

                    // Find all the mappings that 
                    // are now not present in the collection 
                    // and delete them from the repository.
                    if (!this.InstanceMappings.ContainsInstanceMapping(
                        r0["SourceColumnName"].ToString()))
                        toBeDeleted.Add(r0["SourceColumnName"].ToString());
                }

                foreach (InstanceMapping mapping
                    in this.instanceMappings)
                {
                    // Find all the mappings that 
                    // are present in the collection 
                    // and update them in the repository.
                    if (repositoryMappings.Contains(mapping.SourceColumnName))
                        toBeUpdated.Add(mapping);
                    // Find new members in the collection 
                    // and insert them into the repository.
                    else
                        toBeInserted.Add(mapping);
                }

                // Delete the mappings.
                foreach (string mappingToBeDeleted in toBeDeleted)
                {
                    InstanceMapping mapping =
                        new InstanceMapping(
                        this, mappingToBeDeleted, txn);

                    mapping.Delete(txn);
                }

                // Update the mappings.
                foreach (InstanceMapping mapping in toBeUpdated)
                    mapping.Update(txn);

                // Insert new mappings.
                foreach (InstanceMapping mapping in toBeInserted)
                    mapping.Create(txn);

                #endregion

                #region Update the component collections.
                // Get the collections list.
                DataSet collectionsInfo =
                    SqlHelper.ExecuteDataset(txn,
                    CommandType.Text,
                    Resource.GetComponentCollectionTypesAndNames,
                    new SqlParameter[] { new SqlParameter("@componentId", this.Id) });

                List<string[]> collToBeDeleted =
                    new List<string[]>();

                List<InstanceColumnCollection> collToBeUpdated =
                    new List<InstanceColumnCollection>();

                List<InstanceColumnCollection> collToBeInserted =
                    new List<InstanceColumnCollection>();

                foreach (DataRow r0 in collectionsInfo.Tables[0].Rows)
                {
                    // Find all the mappings that 
                    // are now not present in the collection 
                    // and delete them from the repository.
                    if (!this.ColumnCollections.ContainsCollection(
                        r0["CollectionType"].ToString(),
                        r0["CollectionName"].ToString()))
                        collToBeDeleted.Add(
                            new string[] { 
                                r0["CollectionType"].ToString(),
                                r0["CollectionName"].ToString()});
                }

                bool collectionExists;
                foreach (InstanceColumnCollection collection
                    in this.ColumnCollections)
                {
                    collectionExists = false;

                    // Check if this collection is already in the repository.
                    foreach (DataRow r0 in collectionsInfo.Tables[0].Rows)
                    {
                        if (r0["CollectionType"].ToString().Equals(
                            collection.CollectionType,
                            StringComparison.InvariantCultureIgnoreCase) &&
                            r0["CollectionName"].ToString().Equals(
                            collection.Name,
                            StringComparison.InvariantCultureIgnoreCase))
                        {
                            collToBeUpdated.Add(collection);
                            collectionExists = true;
                            break;
                        }
                    }

                    // Find new members in the collection 
                    // and insert them into the repository.
                    if (!collectionExists)
                        collToBeInserted.Add(collection);
                }

                // Delete the collections.
                foreach (string[] coll in collToBeDeleted)
                {
                    InstanceColumnCollection instanceCollection =
                        new InstanceColumnCollection(this, coll[0], coll[1], txn);

                    instanceCollection.Delete(txn);
                }

                // Update the collections.
                foreach (InstanceColumnCollection coll in collToBeUpdated)
                    coll.Update(txn);

                // Insert new collections.
                foreach (InstanceColumnCollection coll in collToBeInserted)
                    coll.Create(txn);

                #endregion

                // Commit the transaction if it is not an input one.
                if (inputTxn == null)
                    txn.Commit();
            }
            catch (Exception ex)
            {
                // Rollback the transaction if it is not an input one.
                if (inputTxn == null)
                    txn.Rollback();
                throw ex;
            }
        }

        #endregion

        internal void ApplyProperties(XmlNode xComponent, bool createAfresh)
        {
            this.componentClassId =
                xComponent.Attributes["ComponentClassId"] == null ?
                string.Empty : xComponent.Attributes["ComponentClassId"].Value;

            // Remove all the scalar properties.
            if (createAfresh)
                this.scalarProperties.Clear();

            // Add scalar properties.
            foreach (XmlNode xProperty in xComponent.SelectNodes("./Properties/Property"))
            {
                string propertyName = xProperty.Attributes["Name"].Value;

                // Create new scalar property.
                InstanceScalarProperty prop;
                if (this.ScalarProperties.ContainsProperty(propertyName))
                    prop = this.ScalarProperties[propertyName];
                else
                    prop = new InstanceScalarProperty(this, propertyName);

                // Apply changes to scalar property.
                prop.ApplyProperties(xProperty);

                // Add it to collection.
                if (!this.ScalarProperties.ContainsProperty(propertyName))
                    this.scalarProperties.Add(prop);
            }

            // Remove the scalar properties not found in the document.
            List<InstanceScalarProperty> propToBeRemoved =
                new List<InstanceScalarProperty>();
            foreach (InstanceScalarProperty property in this.ScalarProperties)
                if (xComponent.SelectSingleNode(
                    "./Properties/Property[@Name=\"" + property.Name + "\"]") == null)
                    propToBeRemoved.Add(property);
            foreach (InstanceScalarProperty property in propToBeRemoved)
                this.ScalarProperties.Remove(property);

            // Remove all the mappings.
            if (createAfresh)
                this.instanceMappings.Clear();

            // Add mappings.
            foreach (XmlNode xMapping in xComponent.SelectNodes("./Mappings/Mapping"))
            {
                string sourceCol = xMapping.Attributes["SourceColumn"].Value;

                // Create new mapping.
                InstanceMapping mapping;
                if (this.InstanceMappings.ContainsInstanceMapping(sourceCol))
                    mapping = this.InstanceMappings[sourceCol];
                else
                    mapping = new InstanceMapping(this, sourceCol);

                // Apply changes to mapping.
                mapping.ApplyProperties(xMapping);

                // Add it to collection.
                if (!this.InstanceMappings.ContainsInstanceMapping(sourceCol))
                    this.instanceMappings.Add(mapping);
            }

            // Remove the mappings not found in the document.
            List<InstanceMapping> mappingsToBeRemoved = new List<InstanceMapping>();
            foreach (InstanceMapping mapping in this.InstanceMappings)
                if (xComponent.SelectSingleNode(
                    "./Mappings/Mapping[@SourceColumn=\"" + mapping.SourceColumnName + "\"]") == null)
                    mappingsToBeRemoved.Add(mapping);
            foreach (InstanceMapping mapping in mappingsToBeRemoved)
                this.InstanceMappings.Remove(mapping);

            // Remove all the column collections.
            if (createAfresh)
                this.columnCollections.Clear();

            // Add mappings.
            foreach (XmlNode xCollection in xComponent.SelectNodes("./CollectionInformation/Collection"))
            {
                string collectionType = xCollection.Attributes["Type"].Value;
                string collectionName = xCollection.Attributes["Name"].Value;

                // Create new collection.
                InstanceColumnCollection collection;
                if (this.ColumnCollections.ContainsCollection(collectionType, collectionName))
                    collection = this.ColumnCollections[collectionType, collectionName];
                else
                    collection =
                        new InstanceColumnCollection(this, collectionType, collectionName);

                // Apply changes to collection.
                collection.ApplyProperties(xCollection, createAfresh);

                // Add it to Collections.
                if (!this.ColumnCollections.ContainsCollection(collectionType, collectionName))
                    this.columnCollections.Add(collection);
            }

            // Remove the collections not found in the document.
            List<InstanceColumnCollection> collectionsToBeRemoved = new List<InstanceColumnCollection>();
            foreach (InstanceColumnCollection collection in this.ColumnCollections)
                if (xComponent.SelectSingleNode(
                    "./CollectionInformation/Collection[@Type=\"" + collection.CollectionType +
                    "\" and @Name=\"" + collection.Name + "\"]") == null)
                    collectionsToBeRemoved.Add(collection);
            foreach (InstanceColumnCollection collection in collectionsToBeRemoved)
                this.ColumnCollections.Remove(collection);
        }

        internal void Delete(SqlTransaction txn)
        {
            // Remove the component from the repository.
            SqlHelper.ExecuteScalar(txn,
                CommandType.StoredProcedure,
                "sp_DeleteInstanceComponent",
                new SqlParameter("@componentId", this.Id));
        }
    }

    public sealed class InstanceConfigurableComponentCollection : IEnumerable
    {
        #region Fields

        private Dictionary<string, InstanceConfigurableComponent>
            configurableComponents =
            new Dictionary<string, InstanceConfigurableComponent>();

        MDDETemplateInstance parent;

        #endregion

        #region Properties

        public MDDETemplateInstance Parent
        {
            get { return parent; }
        }

        public InstanceConfigurableComponent this[string componentUri]
        {
            get { return configurableComponents[componentUri]; }
        }

        #endregion

        #region Methods

        internal InstanceConfigurableComponentCollection(MDDETemplateInstance parent)
        {
            this.parent = parent;
        }

        public void Add(InstanceConfigurableComponent configComp)
        {
            configurableComponents.Add(configComp.ComponentUri, configComp);
        }

        public void Remove(InstanceConfigurableComponent configComp)
        {
            configurableComponents.Remove(configComp.ComponentUri);
        }

        public bool ContainsConfigurableComponent(string componentUri)
        {
            return configurableComponents.ContainsKey(componentUri);
        }

        public void Clear()
        {
            configurableComponents.Clear();
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return configurableComponents.Values.GetEnumerator();
        }

        #endregion
    }

}
